package com.vaadin.thomas.contenttool.data;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import com.vaadin.thomas.contenttool.AbstractInitializer;
import com.vaadin.thomas.contenttool.Config;
import com.vaadin.thomas.contenttool.exceptions.ContentModelException;
import com.vaadin.thomas.contenttool.ui.Util;

/**
 * Manages {@link Model} instances.
 * 
 * @author Thomas Mattsson / Vaadin OY
 */
public class ModelFactory {

	private static final String DEFAULT_INSTANCE = "__default__";
	private static Map<String, Model> instances = new HashMap<String, Model>();
	private static Map<String, Config> configs = new HashMap<String, Config>();

	/**
	 * Must be called before {@link #get(String)}. Usually this method is
	 * invoked at the Servlet level; only one instance of {@link Model} should
	 * exist per web app.
	 * <p>
	 * Configs with <code>null</code> as modelId will be changed to
	 * {@link #DEFAULT_INSTANCE}
	 * 
	 * @see AbstractInitializer
	 * @throws ContentModelException
	 *             if user is trying to initialize a Model with an identical id
	 *             twice, or the data store could not be initiated for whatever
	 *             reason.
	 */
	public static Model initialize(Config c) throws ContentModelException {

		Model m;

		String modelId = c.getModelId();

		if (modelId == null) {
			log("Detected null model id, changing to default", modelId,
					Level.INFO);
			modelId = DEFAULT_INSTANCE;
		}
		log("Initializing Model with id '" + modelId + "'", modelId, Level.INFO);

		synchronized (instances) {
			if (instances.containsKey(modelId)) {
				throw new ContentModelException(
						"Cannot create duplicate Models for " + modelId);
			}

			try {
				m = c.getModelClass().newInstance();
				m.init(c);
			} catch (RuntimeException e) {
				throw new ContentModelException(e);
			} catch (InstantiationException e) {
				throw new ContentModelException(e);
			} catch (IllegalAccessException e) {
				throw new ContentModelException(e);
			}

			instances.put(modelId, m);
			configs.put(modelId, c);
		}
		return m;
	}

	private static void log(String msg, String conf, Level level) {
		Util.log(msg, level, configs.containsKey(conf) ? configs.get(conf)
				: null);
	}

	/**
	 * @return A preconfigured, thread-safe {@link Model} instance, or null.
	 */
	public static Model get(String modelId) {
		if (modelId == null) {
			log("changing 'null' to '" + DEFAULT_INSTANCE + "' in get()",
					modelId, Level.FINE);
			modelId = DEFAULT_INSTANCE;
		}
		if (!instances.containsKey(modelId)) {
			log("Could not find Model with id '" + modelId
					+ "' when trying to get().", null, Level.WARNING);
			return null;
		}

		log("Getting model with id '" + modelId + "'", modelId, Level.FINE);
		return instances.get(modelId == null ? DEFAULT_INSTANCE : modelId);
	}

	/**
	 * Destroys and removes the given Model instance. Usually this method is
	 * invoked at the Servlet level; only one instance of {@link Model} should
	 * exist per web app.
	 * 
	 * @see AbstractInitializer
	 */
	public static void destroy(String modelId) {

		if (modelId == null) {
			log("changing 'null' to '" + DEFAULT_INSTANCE + "' in destroy()",
					modelId, Level.FINE);
			modelId = DEFAULT_INSTANCE;
		}

		synchronized (instances) {
			if (!instances.containsKey(modelId)) {
				log("Could not find Model with id '" + modelId
						+ "' when trying to destroy.", modelId, Level.WARNING);
				configs.remove(modelId);
				return;
			}

			log("Destroying Model with id '" + modelId + "'", modelId,
					Level.INFO);

			instances.get(modelId).destroy();
			log("instance destroyed succesfully", modelId, Level.FINE);

			instances.remove(modelId);
			configs.remove(modelId);
		}
	}
}
